<!DOCTYPE html>
<html>

<head>
	<meta charset="utf-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title></title>
	<style type="text/css">
		.title {
			text-align: center;
		}

		.contral_pannel {
			width: fit-content;
			margin: 0 auto;
		}

		.canvas_wrapper {
			display: flex;
			justify-content: center;
			align-items: center;
		}

		#canvas {}

		.footer {
			text-align: center;
		}

		.footer a {
			color: #000000;
			text-decoration: none;
		}
	</style>
</head>

<body>
	<div class="title">
		<h1>棋盘覆盖算法的JavaScript实现</h1>
	</div>
	<div class="canvas_wrapper">
		<canvas id="canvas" onclick="canvas_click();"></canvas>
	</div>

	<div class="contral_pannel">
		<h3 id="special_cell"></h3>
		<p>K的值(<span id="k_value_show">3</span>)：
			<input type="range" name="points" id="k_value" min="1" max="8" step="1" value="3" onchange="init();" />
		</p>
		<p>棋盘大小(<span id="cell_size_show">40</span>)：
			<input type="range" name="points" id="cell_size" min="4" max="80" step="1" value="40" onchange="init();" />
		</p>
		<p>每步延时：(<span id="speed_show">500</span>)：
			<input type="range" name="speed" id="speed" min="20" max="1000" step="1" value="500"
				onchange="speed_change();" />
		</p>
		<button type="button" onclick="solve();">开始覆盖</button>
		<button type="button" onclick="init();">重置棋盘</button>
	</div>
	<script>
		/*全局变量声明*/
		var k;
		var ctx;
		//每次解算的停顿事件
		var solve_delay = 1.0;
		// 每个棋盘格子的大小
		var cell_size;
		// 棋盘的矩阵， 一个 col_num * row_num 的数组
		var board_array;
		// 棋盘列数
		var col_num;
		// 棋盘行数
		var row_num;
		// 棋盘的像素尺寸
		var width;
		var height;
		// 第一次点击的位置
		var first_x;
		var first_y;
		var border_color = "#000000";
		// 覆盖的块数
		var num;
		// 动画控制
		var interval = undefined;

		/*对棋盘初始化*/
		function init() {
			// 初始化棋盘的参数
			k = Math.ceil(document.getElementById('k_value').value);
			// 更新显示的k值
			document.getElementById('k_value_show').textContent = k;
			// 更新棋盘格子大小
			cell_size = Math.ceil(document.getElementById('cell_size').value);
			// 同步显示的棋盘格子大小
			document.getElementById('cell_size_show').textContent = cell_size;
			// 控制台输出
			console.log(cell_size);
			// 棋盘的列数和行数
			col_num = Math.pow(2, k);
			row_num = Math.pow(2, k);
			// 计算棋盘的像素尺寸
			width = col_num * cell_size;
			height = row_num * cell_size;
			first_x = undefined;
			first_y = undefined;
			num = 0;
			canvas = document.getElementById('canvas');
			document.getElementById('special_cell').textContent = "请点击一个棋盘格作为起始状态的特殊点";
			// 判断浏览器是否支持canvas
			if (canvas.getContext) {
				// 设置canvas的尺寸
				canvas.height = height + 1;
				canvas.width = width + 1;
				// 初始化棋盘数组
				board_array = new Array();
				for (let i = 0; i < row_num; i++) {
					row = new Array(col_num);
					for (let j = 0; j < col_num; j++)
						row[j] = 0;
					board_array.push(row);
				}
				ctx = canvas.getContext('2d');
				drawChessBoard();
			} else {
				alert('你的浏览器不支持canvas，无法运行此应用！');
				return;
			}
		}
		/*生成随机颜色*/
		function canvas_click() {
			var x = event.pageX - canvas.getBoundingClientRect().left;
			var y = event.pageY - canvas.getBoundingClientRect().top;
			// 转换成点击的棋盘格的坐标
			var cell_y = Math.floor(x / cell_size);
			var cell_x = Math.floor(y / cell_size);
			if (first_x == undefined) {
				first_x = cell_x;
				first_y = cell_y;
				fillCell(cell_x, cell_y, 'red');
				console.log("特殊方格的坐标为：", cell_x, ',', cell_y);
				document.getElementById('special_cell').textContent = ("特殊方格的坐标为：" + cell_x + ',' + cell_y);
			}
			console.log(x, y, '棋盘格坐标为:' + cell_x + ',' + cell_y);
		}
		/*生成随机颜色*/
		function genRandomColor() {
			let rand_color = '#';
			for (let i = 0; i < 3; i++) {
				new_channel = Math.round(Math.random() * 255).toString(16)
				rand_color += new_channel.length == 2 ? new_channel : '0' + new_channel;
			}
			return rand_color;
		}
		/*绘制棋盘*/
		function drawChessBoard() {
			let border_offset = 0.5;
			ctx.lineWidth = 1;
			ctx.strokeStyle = border_color;
			for (let row = 0; row < row_num + 1; row++) {
				ctx.beginPath();
				ctx.moveTo(border_offset, row * cell_size + border_offset);
				ctx.lineTo(width + border_offset, row * cell_size + border_offset);
				ctx.stroke();
			}
			for (let col = 0; col < col_num + 1; col++) {
				ctx.beginPath();
				ctx.moveTo(col * cell_size + border_offset, border_offset);
				ctx.lineTo(col * cell_size + border_offset, height + border_offset);
				ctx.stroke();
			}
			console.log('重新绘制了棋盘');
		}
		/*覆盖的入口*/
		function solve() {
			if (first_x == undefined) {
				alert("必须设置一个起始的特殊格");
			} else {
				ctx.clearRect(0, 0, width + 1, height + 1);
				fillCell(first_x, first_y, 'red');
				chessBoard(0, 0, first_x, first_y, row_num);
				drawBorder();
			}
		}

		function chessBoard(tr, tc, dr, dc, size) {
			let s, t;
			let randomColor = genRandomColor();
			if (size == 1) return;
			s = Math.floor(size / 2); //分割棋盘
			t = ++num; //L型骨牌号
			if (dr < tr + s && dc < tc + s) //覆盖左上角子棋盘
			{
				//特殊方格在此棋盘中
				chessBoard(tr, tc, dr, dc, s);
			} else //此棋盘中无特殊方格
			{
				//用t号L型骨牌覆盖右下角
				board_array[tr + s - 1][tc + s - 1] = t;
				fillCell(tr + s - 1, tc + s - 1, randomColor)
				//覆盖其余方格
				chessBoard(tr, tc, tr + s - 1, tc + s - 1, s);
			}
			//覆盖右上角子棋盘
			if (dr < tr + s && dc >= tc + s) //
			{
				//特殊方格在此棋盘中
				chessBoard(tr, tc + s, dr, dc, s);
			} else //此棋盘中无特殊方格
			{
				//用t号L型骨牌覆盖左下角
				board_array[tr + s - 1][tc + s] = t;
				fillCell(tr + s - 1, tc + s, randomColor)
				//覆盖其余方格
				chessBoard(tr, tc + s, tr + s - 1, tc + s, s);
			}
			//覆盖左下角子棋盘
			if (dr >= tr + s && dc < tc + s) {
				//特殊方格在此棋盘中
				chessBoard(tr + s, tc, dr, dc, s);
			} else {
				//用t号L型骨牌覆盖右上角
				board_array[tr + s][tc + s - 1] = t;
				fillCell(tr + s, tc + s - 1, randomColor)
				//覆盖其余方格
				chessBoard(tr + s, tc, tr + s, tc + s - 1, s);
			}
			//覆盖右下角子棋盘
			if (dr >= tr + s && dc >= tc + s) {
				//特殊方格在此棋盘中
				chessBoard(tr + s, tc + s, dr, dc, s);
			} else {
				//用t号L型骨牌覆盖左上角
				board_array[tr + s][tc + s] = t;
				fillCell(tr + s, tc + s, randomColor)
				//覆盖其余方格
				chessBoard(tr + s, tc + s, tr + s, tc + s, s);
			}
		}

		/*填充棋盘格*/
		function fillCell(x, y, corlor) {
			ctx.fillStyle = corlor;
			ctx.fillRect(y * cell_size, x * cell_size, cell_size, cell_size);
		}

		/*方格边界的颜色*/
		function drawBorder() {
			let border_offset = 0.5;
			ctx.lineWidth = 1;
			ctx.strokeStyle = 'white';
			for (let i = 0; i < row_num; i++) {
				for (let j = 0; j < col_num; j++) {
					//和左边的方格颜色进行比较
					if (j > 0 && (board_array[i][j] != board_array[i][j - 1])) {
						// 开始一个新的路径或重置当前路径
						ctx.beginPath();
						// 将笔触移动到指定的坐标点
						ctx.moveTo(j * cell_size + border_offset, i * cell_size + border_offset);
						// 从当前位置绘制一条直线到指定的坐标点
						ctx.lineTo(j * cell_size + border_offset, (i + 1) * cell_size + border_offset);
						// 绘制已定义的路径
						ctx.stroke();
					}
					//和上边的方格颜色进行比较
					if (i > 0 && (board_array[i][j] != board_array[i - 1][j])) {
						// 开始一个新的路径或重置当前路径
						ctx.beginPath();
						// 将笔触移动到指定的坐标点
						ctx.moveTo(j * cell_size + border_offset, i * cell_size + border_offset);
						// 从当前位置绘制一条直线到指定的坐标点
						ctx.lineTo((j + 1) * cell_size + border_offset, i * cell_size + border_offset);
						// 绘制已定义的路径
						ctx.stroke();
					}
				}
			}
			// 绘制最外层边框
			// 开始一个新的路径或重置当前路径
			ctx.beginPath();
			// 将笔触移动到画布的左上角起始点
			ctx.moveTo(border_offset, border_offset);
			// 从起始点绘制一条直线到画布的左下角
			ctx.lineTo(border_offset, width + border_offset);
			// 从左下角绘制一条直线到画布的右下角
			ctx.lineTo(width + border_offset, width + border_offset);
			// 这一行代码是多余的，因为它重复了上一行的操作，没有改变路径
			ctx.lineTo(width + border_offset, width + border_offset);
			// 从右下角绘制一条直线到画布的右上角
			ctx.lineTo(width + border_offset, border_offset);
			// 从右上角绘制一条直线回到起始点，完成闭合路径
			ctx.lineTo(border_offset, border_offset);
			// 绘制已定义的路径
			ctx.stroke();
		}

		function speed_change() {
			solve_delay = parseFloat(document.getElementById('speed').value);
		}

		init();
	</script>

	<div id="footer" class="footer"
		style="bottom: 0px; width: 100%;position: fixed;z-index: -10;font-family: Consolas;">
		<style type="text/css">
			.footer {
				text-align: center;
			}

			.footer a {
				color: #000000;
				text-decoration: none;
			}
		</style>

		<script type="text/javascript">
			function setPosition() {
				if (window.innerHeight - 70 > document.body.clientHeight) {
					document.getElementById('footer').style.position = 'fixed';
				} else {
					document.getElementById('footer').style.position = 'relative';
				}
			}
			document.body.onresize = setPosition;
			window.onresize = setPosition;
			setPosition();
		</script>
		<script>
			var _hmt = _hmt || [];
			(function () {
				var hm = document.createElement("script");
				hm.src = "https://hm.baidu.com/hm.js?d564375877ebc5c0c21628a5ce920dac";
				var s = document.getElementsByTagName("script")[0];
				s.parentNode.insertBefore(hm, s);
			})();
		</script>
	</div>
</body>

</html>